﻿namespace MetaProperties.Core
{
    using System.Collections.Generic;
    using System.ComponentModel;

    using MetaProperties.Framework;

#if ANCESTRY_SUPPORTED
    using MetaProperties.Framework.Ancestry;
#endif

    /// <summary>
    /// Base class for typed observable values.
    /// </summary>
    /// <typeparam name="T">The type of the contained value.</typeparam>
    public abstract class TypedObservable<T> : UntypedObservable
    {
        /// <summary>
        /// The name of the value property.
        /// </summary>
        private const string ValuePropertyName = "Value";

        /// <summary>
        /// The comparer for the value.
        /// </summary>
        private readonly IComparer<T> comparer;

        /// <summary>
        /// Initializes a new instance of the <see cref="TypedObservable&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="comparer">The comparer.</param>
        protected TypedObservable(IComparer<T> comparer)
            : this()
        {
            this.comparer = comparer;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TypedObservable&lt;T&gt;"/> class.
        /// </summary>
        protected TypedObservable()
        {
            IObservable<T> notifyingThis = this as IObservable<T>;
            if (notifyingThis == null)
            {
                throw new InterfaceNotImplementedException("TypedObservable implementation does not implement IObservable<T>: " + this.GetType().FullName);
            }

            this.MetaProperty = new MetaProperty<T>(notifyingThis);
        }

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public override event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Occurs when a property value is changing.
        /// </summary>
        public override event PropertyChangingEventHandler PropertyChanging;

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public override event ValueChangedEventHandler UntypedValueChanged;

        /// <summary>
        /// Occurs when a property value is set.
        /// </summary>
        public override event ValueChangedEventHandler UntypedValueSet;

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event ValueChangedEventHandler<T> ValueChanged;

        /// <summary>
        /// Occurs when a property value is set.
        /// </summary>
        public event ValueChangedEventHandler<T> ValueSet;

        /// <summary>
        /// Gets the meta property.
        /// </summary>
        /// <value>The meta property.</value>
        public IMetaProperty<T> MetaProperty
        {
            get;
            private set;
        }

#if ANCESTRY_SUPPORTED
        protected void UpdateValueParent(T value, UpdateParentOperation operation)
        {
            IParentedMetaPropertyHost hostValue = value as IParentedMetaPropertyHost;
            if (hostValue != null)
            {
                hostValue.UpdateParent(this.MetaProperty.Host, operation);
            }
        }
#endif

        /// <summary>
        /// Compares two values for equality.
        /// </summary>
        /// <param name="first">The first value.</param>
        /// <param name="second">The second value.</param>
        /// <returns>True if the two values are equal; false otherwise.</returns>
        protected bool AreEqual(T first, T second)
        {
            if (this.comparer == null)
            {
                return object.Equals(first, second);
            }
            else
            {
                return this.comparer.Compare(first, second) == 0;
            }
        }

        /// <summary>
        /// Called when the value is changing to raise the appropriate events.
        /// </summary>
        protected void OnValueChanging()
        {
            var handler = this.PropertyChanging;
            if (handler != null)
            {
                handler(this, new PropertyChangingEventArgs(ValuePropertyName));
            }
        }

        /// <summary>
        /// Called when the value changes to raise the appropriate events.
        /// </summary>
        /// <param name="oldValue">The old value.</param>
        /// <param name="newValue">The new value.</param>
        protected void OnValueChanged(T oldValue, T newValue)
        {
            {
                var handler = this.PropertyChanged;
                if (handler != null)
                {
                    PropertyChangedEventArgs propertyChangedArgs = new PropertyChangedEventArgs(ValuePropertyName);
                    handler(this, propertyChangedArgs);
                }
            }

            ValueChangedEventArgs<T> changedArgs = null;
            {
                var handler = this.ValueChanged;
                if (handler != null)
                {
                    changedArgs = new ValueChangedEventArgs<T>(oldValue, newValue);
                    handler(this, changedArgs);
                }
            }

            {
                var handler = this.UntypedValueChanged;
                if (handler != null)
                {
                    if (changedArgs == null)
                    {
                        changedArgs = new ValueChangedEventArgs<T>(oldValue, newValue);
                    }

                    handler(this, changedArgs);
                }
            }

            ValueChangedEventArgs<T> setArgs = null;
            {
                var handler = this.ValueSet;
                if (handler != null)
                {
                    setArgs = new ValueChangedEventArgs<T>(oldValue, newValue);
                    handler(this, setArgs);
                }
            }

            {
                var handler = this.UntypedValueSet;
                if (handler != null)
                {
                    if (setArgs == null)
                    {
                        setArgs = new ValueChangedEventArgs<T>(oldValue, newValue);
                    }
                    handler(this, setArgs);
                }
            }
        }

        /// <summary>
        /// Called when the value is set and raises the appropriate events.
        /// </summary>
        /// <param name="value">The value.</param>
        protected void OnValueSet(T value)
        {
            ValueChangedEventArgs<T> args = null;
            {
                var handler = this.ValueSet;
                if (handler != null)
                {
                    args = new ValueChangedEventArgs<T>(value, value);
                    handler(this, args);
                }
            }

            {
                var handler = this.UntypedValueSet;
                if (handler != null)
                {
                    if (args == null)
                    {
                        args = new ValueChangedEventArgs<T>(value, value);
                    }

                    handler(this, args);
                }
            }
        }

        /// <summary>
        /// Gets the MetaProperty.
        /// </summary>
        /// <returns>The MetaProperty.</returns>
        protected override IMetaProperty GetMetaProperty()
        {
            return this.MetaProperty;
        }
    }
}
